package com.sharp.portal.utils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.pagehelper.PageInfo;
import com.sharp.framework.config.anno.EnumDict;
import com.sharp.framework.frame.cache.base.IDynamicDict;
import com.sharp.framework.service.cache.FrameCacheService;
import com.sharp.framework.utils.DictEnumUtil;
import com.sharp.kernel.constant.CommonConst;
import com.sharp.kernel.constant.db.SqlConstant;
import com.sharp.kernel.constant.dict.MetaDict;
import com.sharp.kernel.constant.err.ErrCodeSys;
import com.sharp.kernel.mybatis.dao.entity.Columns;
import com.sharp.kernel.mybatis.repository.ExampleFactory;
import com.sharp.kernel.utils.BeanUtil;
import com.sharp.kernel.utils.ReflectionUtil;
import com.sharp.kernel.utils.StringUtil;
import com.sharp.kernel.validate.Validator;
import com.sharp.portal.constant.anno.*;
import com.sharp.portal.constant.dict.PortalSortDict;
import com.sharp.portal.dao.entity.CoPortalLayout;
import com.sharp.portal.vo.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import tk.mybatis.mapper.entity.Example;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Title: PortalUtil
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2020/11/5 14:50
 */
@Slf4j
public class PortalUtil {


    private static final Map<String, String> DATA_TYPE_MAP = new ConcurrentHashMap<>();

    static {
        DATA_TYPE_MAP.put("varchar", PortalField.DataType.STRING.name());
        DATA_TYPE_MAP.put("longtext", PortalField.DataType.TEXT.name());

        DATA_TYPE_MAP.put("int", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("tinyint", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("mediumint", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("bigint", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("float", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("double", PortalField.DataType.NUMBER.name());
        DATA_TYPE_MAP.put("decimal", PortalField.DataType.NUMBER.name());

        DATA_TYPE_MAP.put("date", PortalField.DataType.DATETIME.name());
        DATA_TYPE_MAP.put("time", PortalField.DataType.DATETIME.name());
        DATA_TYPE_MAP.put("year", PortalField.DataType.DATETIME.name());
        DATA_TYPE_MAP.put("datetime", PortalField.DataType.DATETIME.name());
        DATA_TYPE_MAP.put("timestamp", PortalField.DataType.DATETIME.name());
    }

    private PortalUtil() {

    }

    public static ModelDisplayVO getModelDisplay(Class<?> aClass) {
        ModelDisplayVO vo = new ModelDisplayVO();
        PortalModel portalModelAnnotation = aClass.getAnnotation(PortalModel.class);
        Validator.assertNotNull(portalModelAnnotation, ErrCodeSys.PA_DATA_NOT_EXIST, "后台管理设置");

        vo.setValue(portalModelAnnotation.value());
        vo.setName(portalModelAnnotation.description());
        vo.setReadOnly(StringUtil.convertSwitch(portalModelAnnotation.readOnly()));
        if (!portalModelAnnotation.readOnly()) {
            vo.setAddButton(StringUtil.convertSwitch(portalModelAnnotation.add()));
            vo.setModifyButton(StringUtil.convertSwitch(portalModelAnnotation.modify()));
            vo.setCopyButton(StringUtil.convertSwitch(portalModelAnnotation.copy()));
            vo.setDisableButton(StringUtil.convertSwitch(portalModelAnnotation.disable()));
            vo.setDeleteButton(StringUtil.convertSwitch(portalModelAnnotation.delete()));
            vo.setUploadButton(StringUtil.convertSwitch(portalModelAnnotation.upload()));
            vo.setRefreshButton(StringUtil.convertSwitch(portalModelAnnotation.refresh()));
        }
        vo.setExportButton(StringUtil.convertSwitch(portalModelAnnotation.export()));

        PortalModelTree portalModelTreeAnnotation = aClass.getAnnotation(PortalModelTree.class);
        if (portalModelTreeAnnotation != null) {
            vo.setTreeFormat(CommonConst.YES);
            vo.setUploadButton(CommonConst.NO);
            vo.setExportButton(CommonConst.NO);
        } else {
            vo.setTreeFormat(CommonConst.NO);
        }

        Map<String, Field> fieldMap = ReflectionUtil.getFieldMap(aClass);
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            if (entry.getValue().getAnnotation(PortalId.class) != null) {
                vo.setIdField(entry.getKey());
            }
            if (entry.getValue().getAnnotation(PortalPid.class) != null) {
                vo.setPidField(entry.getKey());
            }
        }

        return vo;
    }

    public static <ENTITY, VO> List<TreeDisplayVO> getTreeDisplay(Class<VO> aClass, List<ENTITY> dataList) {
        String childFieldName = null;
        String parentFieldName = null;
        String displayName;
        PortalModelTree portalModelTreeAnnotation = aClass.getAnnotation(PortalModelTree.class);
        if (portalModelTreeAnnotation == null) {
            return new ArrayList<>();
        } else {
            displayName = portalModelTreeAnnotation.displayField();
        }
        Map<String, Field> fieldMap = ReflectionUtil.getFieldMap(aClass);
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            if (entry.getValue().getAnnotation(PortalId.class) != null) {
                childFieldName = entry.getKey();
            }
            if (entry.getValue().getAnnotation(PortalPid.class) != null) {
                parentFieldName = entry.getKey();
            }
        }

        Validator.assertNotBlank(childFieldName, ErrCodeSys.PA_DATA_NOT_EXIST, "子节点字段名配置");
        Validator.assertNotBlank(parentFieldName, ErrCodeSys.PA_DATA_NOT_EXIST, "父节点字段名配置");
        Validator.assertNotBlank(displayName, ErrCodeSys.PA_DATA_NOT_EXIST, "显示字段名配置");
        List<TreeItem> treeItemList = new ArrayList<>();
        try {
            if (CollectionUtils.isNotEmpty(dataList)) {
                for (ENTITY data : dataList) {
                    TreeItem vo = new TreeItem();
                    vo.setId(ReflectionUtil.getValue(data, childFieldName, Object.class));
                    vo.setPid(ReflectionUtil.getValue(data, parentFieldName, Object.class));
                    vo.setDisplayName(ReflectionUtil.getValue(data, displayName, String.class));
                    treeItemList.add(vo);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return ReflectionUtil.buildTree(TreeDisplayVO.class, "subList", TreeItem.class, treeItemList, "id", "pid");
    }

    public static List<ConditionVO> fillCondition(HttpServletRequest request) {
        List<ConditionVO> extraConditionList = new ArrayList<>();
        if (MapUtils.isNotEmpty(request.getParameterMap())) {
            for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
                if (entry.getValue() != null) {
                    ConditionVO vo = new ConditionVO();
                    vo.setProperty(entry.getKey());
                    if (entry.getValue().length > 1) {
                        vo.setRelation(SqlConstant.IN);
                        vo.setValueList(Arrays.asList(entry.getValue()));
                    } else {
                        if (StringUtils.isNotBlank(entry.getValue()[0])) {
                            vo.setRelation(SqlConstant.EQUAL);
                            vo.setValue(entry.getValue()[0]);
                        } else {
                            vo.setRelation(SqlConstant.NULL);
                        }
                    }
                    extraConditionList.add(vo);

                }
            }
        }
        return extraConditionList;
    }

    @SuppressWarnings("rawtypes")
    public static <ENTITY, SERVICE extends ExampleFactory> Example buildExampleByQueryConditionReq(
            QueryConditionReq req, SERVICE service, Class<ENTITY> entityClazz) {
        Map<String, String> fieldMap = ReflectionUtil.getFieldDisplayMap(entityClazz);

        List<ConditionVO> conditions = req.getConditionList();

        Example example = service.getExample();
        Example.Criteria criteria = example.createCriteria();

        if (CollectionUtils.isNotEmpty(conditions)) {
            for (ConditionVO condition : conditions) {
                if (fieldMap.containsKey(condition.getProperty())) {
                    if (StringUtils.isNotBlank(condition.getValue())) {
                        criteria = service.buildCriteria(example, condition.getProperty(), condition.getValue(),
                                condition.getRelation());
                    } else if (CollectionUtils.isNotEmpty(condition.getValueList())) {
                        criteria = service.buildCriteria(example, condition.getProperty(), condition.getValueList(),
                                condition.getRelation());
                    } else if (condition.getRelation().equals(SqlConstant.NOT_NULL) ||
                            condition.getRelation().equals(SqlConstant.NULL)) {
                        criteria = service.buildCriteria(example, condition.getProperty(), condition.getValue(),
                                condition.getRelation());
                    }
                    example.and(criteria);
                }
            }
        }
        if (req.getSort() != null) {
            if (fieldMap.containsKey(req.getSort().getProperty())) {
                if (req.getSort().getType() != null) {
                    if (PortalSortDict.DESC.getValue().equals(req.getSort().getType())) {
                        example.orderBy(req.getSort().getProperty()).desc();
                    } else {
                        example.orderBy(req.getSort().getProperty()).asc();
                    }
                }
            }
        }
        return example;
    }

    public static RelationModelVO getRelationModel(Class<?> aClass) {
        RelationModelVO vo = new RelationModelVO();

        Map<String, Field> fieldMap = ReflectionUtil.getFieldMap(aClass);
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            Field field = entry.getValue();
            PortalRelationMaster portalRelationMasterAnnotation = field.getAnnotation(PortalRelationMaster.class);
            PortalRelationSlave portalRelationSlaveAnnotation = field.getAnnotation(PortalRelationSlave.class);
            PortalRelationOrder portalRelationOrderAnnotation = field.getAnnotation(PortalRelationOrder.class);

            if (portalRelationMasterAnnotation != null) {
                vo.setMasterIdFieldName(entry.getKey());
                vo.setMasterIdOriginalFieldName(portalRelationMasterAnnotation.originalFieldName());
            } else if (portalRelationSlaveAnnotation != null) {
                vo.setSlaveIdFieldName(entry.getKey());
                vo.setSlaveIdOriginalFieldName(portalRelationSlaveAnnotation.originalFieldName());
            } else if (portalRelationOrderAnnotation != null) {
                vo.setOrderFieldName(entry.getKey());
            }
        }
        Validator.assertNotBlank(vo.getMasterIdFieldName(), ErrCodeSys.PA_DATA_NOT_EXIST, "后台管理关系主属性设置");
        Validator.assertNotBlank(vo.getSlaveIdFieldName(), ErrCodeSys.PA_DATA_NOT_EXIST, "后台管理关系从属性设置");
        return vo;
    }

    public static <T, K> PageInfo<T> convert(PageInfo<K> source, Class<T> destClazz) {
        PageInfo<T> res = new PageInfo(source.getList());
        if (CollectionUtils.isNotEmpty(source.getList())) {
            List<T> dataList = convert(source.getList(), destClazz);
            res.setList(dataList);
        }
        res.setTotal(source.getTotal());
        return res;
    }

    public static <T, K> List<T> convert(List<K> srcList, Class<T> destClazz) {
        List<T> dataList = new ArrayList<>();
        for (K sourceData : srcList) {
            T descData = ReflectionUtil.newInstance(destClazz);
            convert(sourceData, descData);
            dataList.add(descData);
        }
        return dataList;
    }

    public static <T, K> void convert(K srcData, T destData) {
        for (Map.Entry<String, Field> entry : ReflectionUtil.getFieldMap(destData.getClass()).entrySet()) {
            Object value = ReflectionUtil.getValue(srcData, entry.getKey(), Object.class);
            if (value != null) {
                EnumDict dictAnnotation = entry.getValue().getAnnotation(EnumDict.class);
                if (dictAnnotation != null) {
                    if (value instanceof String) {
                        value = PortalUtil.convertDictDisplay(value, dictAnnotation);
                    } else {
                        String display = PortalUtil.convertDictDisplay(value, dictAnnotation);
                        String targetFieldName = entry.getKey() + "Display";
                        if (StringUtils.isNotBlank(dictAnnotation.targetFieldName())) {
                            targetFieldName = dictAnnotation.targetFieldName();
                        }
                        ReflectionUtil.setValue(destData, targetFieldName, display);
                    }
                }
                PortalConvert convertAnnotation = entry.getValue().getAnnotation(PortalConvert.class);
                if (convertAnnotation != null) {
                    Class<?> serviceClass = convertAnnotation.serviceClass();
                    try {
                        Object serviceBean = BeanUtil.getBean(serviceClass);
                        Method method = ReflectionUtil.getMethod(serviceBean.getClass(), convertAnnotation.method(),
                                entry.getValue().getType());
                        Object targetValue = ReflectionUtil.invoke(serviceBean, method, value);
                        ReflectionUtil.setValue(destData, convertAnnotation.targetFieldName(), targetValue);
                    } catch (Exception e) {
                        log.error("转换错误", e);
                    }
                }
                ReflectionUtil.setValue(destData, entry.getKey(), value);
            }
        }
    }

    public static <E extends Enum<E>> String convertDictDisplay(Object value, EnumDict dictAnnotation) {
        if (dictAnnotation != null) {
            Class<?> clazz = dictAnnotation.dict();
            if (Enum.class.isAssignableFrom(clazz)) {
                E eEnum = DictEnumUtil.getEnum(value, dictAnnotation.valueFieldName(), clazz);
                return ReflectionUtil.getValue(eEnum, dictAnnotation.displayFieldName(), String.class);
            } else if (IDynamicDict.class.isAssignableFrom(clazz)) {
                String dictId = clazz.getAnnotation(MetaDict.class).value();
                try {
                    return BeanUtil.getBean(FrameCacheService.class).getDictLabelByValue(dictId, String.valueOf(value));
                } catch (Exception e) {
                    return StringUtil.parse(value);
                }
            }
        }
        return value.toString();

    }

    public static String[] buildExampleSelectProperties(Class<?> aClass) {
        List<String> properties = new ArrayList<>();
        Map<String, Field> fieldMap = ReflectionUtil.getFieldMap(aClass);
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            Field field = entry.getValue();
            PortalRelationMaster portalRelationMasterAnnotation = field.getAnnotation(PortalRelationMaster.class);
            PortalRelationSlave portalRelationSlaveAnnotation = field.getAnnotation(PortalRelationSlave.class);
            if (portalRelationMasterAnnotation != null) {
                continue;
            }
            properties.add(entry.getKey());
        }
        return properties.toArray(new String[0]);
    }

    public static LinkedHashMap<String, FieldDisplayVO> getFieldDisplayMap(Class<?> aClass) {
        Map<String, Field> fieldMap = ReflectionUtil.getFieldMap(aClass);
        LinkedHashMap<String, FieldDisplayVO> res = new LinkedHashMap<>(fieldMap.size());
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            try {
                Field field = entry.getValue();
                FieldDisplayVO vo = new FieldDisplayVO();
                // 忽略jsonIgnore注解
                JsonIgnore jsonIgnoreAnnotation = field.getAnnotation(JsonIgnore.class);
                if (jsonIgnoreAnnotation != null) {
                    continue;
                }
                // 根据JsonProperty设置字段名
                JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);
                if (jsonPropertyAnnotation != null && StringUtils.isNotEmpty((jsonPropertyAnnotation).value())) {
                    vo.setProperty(jsonPropertyAnnotation.value());
                } else {
                    vo.setProperty(field.getName());
                }
                // 根据类型设置dataType
                if (String.class.isAssignableFrom(field.getType())) {
                    vo.setDataType(PortalField.DataType.STRING.name());
                } else if (Number.class.isAssignableFrom(field.getType())) {
                    vo.setDataType(PortalField.DataType.NUMBER.name());
                } else if (Date.class.isAssignableFrom(field.getType())) {
                    vo.setDataType(PortalField.DataType.DATE.name());
                } else if (Boolean.class.isAssignableFrom(field.getType())) {
                    vo.setDataType(PortalField.DataType.BOOLEAN.name());
                } else {
                    vo.setDataType(PortalField.DataType.STRING.name());
                }
                // 根据apiModelProperty设置显示名称
                ApiModelProperty apiModelAnnotation = field.getAnnotation(ApiModelProperty.class);
                if (apiModelAnnotation != null) {
                    if (StringUtils.isNotBlank((apiModelAnnotation).value())) {
                        vo.setDisplayName(apiModelAnnotation.value());
                    } else {
                        vo.setDisplayName(field.getName());
                    }
                }
                // 默认字段可修改
                vo.setReadOnly(CommonConst.NO);
                vo.setRequired(CommonConst.NO);
                vo.setHidden(CommonConst.NO);
                // 根据portalField注解配置其他参数
                PortalField portalAnnotation = field.getAnnotation(PortalField.class);
                EnumDict enumDictAnnotation = field.getAnnotation(EnumDict.class);
                if (portalAnnotation != null) {
                    if (!portalAnnotation.dataType().equals(PortalField.DataType.DEFAULT)) {
                        vo.setDataType(portalAnnotation.dataType().name());
                    }
                    if (portalAnnotation.dataType().equals(PortalField.DataType.ENUM) ||
                            portalAnnotation.dataType().equals(PortalField.DataType.TREE)) {
                        vo.setReference(portalAnnotation.reference());
                        if (!field.getType().equals(String.class)) {
                            vo.setWriteOnly(CommonConst.YES);
                        }
                    }
                    vo.setReadOnly(StringUtil.convertSwitch(portalAnnotation.readOnly()));
                    vo.setRequired(StringUtil.convertSwitch(portalAnnotation.required()));
                    vo.setHidden(StringUtil.convertSwitch(portalAnnotation.hidden()));
                } else if (enumDictAnnotation != null) {
                    vo.setDataType(PortalField.DataType.ENUM.name());
                    vo.setReference(enumDictAnnotation.dict().getAnnotation(MetaDict.class).value());
                    if (!field.getType().equals(String.class)) {
                        vo.setWriteOnly(CommonConst.YES);
                    }
                }
                // id, pid字段不可手动修改
                PortalId portalIdAnnotation = field.getAnnotation(PortalId.class);
                vo.setIdField(StringUtil.convertSwitch(portalIdAnnotation != null));
                PortalPid portalPidAnnotation = field.getAnnotation(PortalPid.class);
                vo.setPidField(StringUtil.convertSwitch(portalPidAnnotation != null));
                if (portalIdAnnotation != null || portalPidAnnotation != null) {
                    if (!StringUtil.convertSwitch(vo.getRequired())) {
                        vo.setHidden(CommonConst.YES);
                    }
                }

                PortalRelationMaster portalRelationMasterAnnotation = field.getAnnotation(PortalRelationMaster.class);
                PortalRelationSlave portalRelationSlaveAnnotation = field.getAnnotation(PortalRelationSlave.class);
                if (portalRelationMasterAnnotation != null || portalRelationSlaveAnnotation != null) {
                    vo.setHidden(CommonConst.YES);
                }

                res.put(field.getName(), vo);
            } catch (SecurityException | IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
        return res;
    }

    public static CoPortalLayout buildCoPortalLayout(String path, Columns column) {
        String property = StringUtil.underlineToCamel(column.getColumnName());
        CoPortalLayout coPortalLayout = new CoPortalLayout();
        coPortalLayout.setPath(path);
        coPortalLayout.setProperty(property);
        coPortalLayout.setDisplayName(column.getColumnComment());
        coPortalLayout.setRequired(CommonConst.NO);
        if (property.startsWith("id") || property.endsWith("id") ||
                StringUtils.equals(property, SqlConstant.VALID_FIELD)) {
            coPortalLayout.setHidden(CommonConst.YES);
        } else {
            coPortalLayout.setHidden(CommonConst.NO);
        }
        if (StringUtils.containsAny(property, SqlConstant.ID_FIELD, SqlConstant.CREATE_FIELD, SqlConstant.UPDATE_FIELD,
                SqlConstant.VALID_FIELD)) {
            coPortalLayout.setUpload(CommonConst.NO);
        } else {
            coPortalLayout.setUpload(CommonConst.YES);
        }
        coPortalLayout.setReference(StringUtil.EMPTY);
        coPortalLayout.setReadOnly(CommonConst.NO);
        coPortalLayout.setWriteOnly(CommonConst.NO);
        coPortalLayout.setDataType(
                DATA_TYPE_MAP.getOrDefault(column.getDataType(), PortalField.DataType.STRING.name()));
        return coPortalLayout;
    }

    public static LinkedHashMap<String, FieldDisplayVO> getFieldDisplayMap(List<CoPortalLayout> coPortalLayoutList) {
        LinkedHashMap<String, FieldDisplayVO> map = new LinkedHashMap<>();
        if (CollectionUtils.isNotEmpty(coPortalLayoutList)) {
            for (CoPortalLayout coPortalLayout : coPortalLayoutList) {
                FieldDisplayVO vo = ReflectionUtil.copy(coPortalLayout, FieldDisplayVO.class);
                if (StringUtils.isNotBlank(vo.getReference())) {
                    vo.setDataType(PortalField.DataType.ENUM.name());
                }
                vo.setHidden(coPortalLayout.getHidden());
                if (!StringUtil.convertSwitch(coPortalLayout.getRequired())) {
                    if (StringUtil.convertSwitch(coPortalLayout.getIdField()) ||
                            StringUtil.convertSwitch(coPortalLayout.getPidField())) {
                        vo.setHidden(CommonConst.YES);
                    }
                }
                map.put(vo.getProperty(), vo);
            }
        }
        return map;
    }
}
